#ifndef _SFEC_H_
#define _SFEC_H_

#ifndef _WIN32_64_
  #define _WIN32_64_
#endif

// 1. Globals
#include <vector>
#include <queue>
#include <map>
#include <sstream>
#include <algorithm>
#include <string>
#ifdef _WIN32_64_
  #include <windows.h>
  #include <ostream>
  #include <MMSystem.h>
#else
  #include <time.h>
  #include <iomanip>
#endif

using namespace std;

// 1a. Vars

class CSimpleCmdLine;
extern CSimpleCmdLine* pCmdLine;

class COnlyOnce;
extern COnlyOnce Can;

// y2009 cmd line params
extern int gBufSize; // in msec
extern int gVolume; // 0-100
extern HWND gCallerWindowHandle;
extern int gNotifyPeriod; // how often we should call PostMessage() - once every gNotifyPeriod seconds
extern int gNotifyMsgID; // notify message id
extern int gNotifyClientID; // notify client id, 6-bit
extern int gDisplayStats; // show stats: 1 - yes, 0 - no (default)

// 1b. Functions

int Saturate(int x, int lo, int hi);
bool getval(const string s, int& val);
bool getval(const string s, int& val, int defval);

// 1c. Classes
using namespace std;
class CSimpleCmdLine{ public: string cmd; // -s stringparam -i 10 -b
  CSimpleCmdLine(string cmdline) : cmd(cmdline) {
    if (cmd.length()==0) return;
    int i; string param, val, s=ToLower(cmd+" "); bool isparam = false,isvalue = false, wasspace = false, forced = false;
    for(i=0; i<s.length(); i++) {
      if (wasspace) {if(s[i]==' ') continue; wasspace = false;}
      if ( !isparam && ((s[i]=='-')||(s[i]=='/')) )
      { if(isvalue) {isvalue = false; M[param] = val;}
        isparam=true; param=""; continue;
      } else if ( s[i]==' ')
      { wasspace = true; if(isparam || forced)
        { isparam = false; isvalue = !forced; M[param] = val = ""; if(forced) param=""; continue;} else
        if(isvalue) {M[param] = val; param=""; val=""; isvalue = false; continue; }
      } else { if(isvalue) { val+=s[i]; continue; } else { forced = !isparam; param+=s[i]; }}}};
  string GetStrParam(string param) { param = ToLower(param); if(M.find(param)==M.end()) return "n//a"; else return M[param];};
  int GetIntParam(string param, int def){ string val = GetStrParam(param); if(val=="n//a")return def; int res; getval(val, res, def); return res;};
  bool GetParam(string param){return GetStrParam(param)!="n//a";};
private: map<string, string> M;
  string ToLower(string s) {/*transform(s.begin(), s.end(), s.begin(), tolower);*/ return s;};
  bool getval(const string s, int& val) {istringstream is(s);is >> val;return !is.fail();};
  bool getval(const string s, int& val, int defval) {bool res;val = defval;res = getval(s, val);if(!res) val = defval;return res;};
};

class CMyDebug { public:
  FILE* flog;LARGE_INTEGER fq,ct;
  CMyDebug(string fname, bool append=true)
    {QueryPerformanceFrequency(&fq);if(!append) while(true){flog = fopen(fname.c_str(), "r"); if(flog!=NULL){fclose(flog);fname+="1";}else break;} flog = fopen(fname.c_str(), "wt");};
  string inttostr(int n){char buf[32]; sprintf(buf, "%d", n); return string(buf);}
  void printsome(string title, unsigned char* buf, int n, int atmost) {
    int i; unsigned char c; if (atmost<n) n=atmost; fprintf(flog,"%s: ",title.c_str());
    for(i=0; i<n; i++) fprintf(flog,"%4d", buf[i]); fprintf(flog," (");
    for(i=0; i<n; i++) {c=buf[i]; if (c<32) c='_'; fprintf(flog,"%c",c);}
    fprintf(flog,")\n"); fflush(flog);};
  void printstr(string s) {fprintf(flog,"%s\n",s.c_str());fflush(flog);}
  void printstr0(string s) {fprintf(flog,"%s",s.c_str());fflush(flog);}
  void printint(int x) {fprintf(flog,"%u\n",x);fflush(flog);}
  void printstrint(string s, int x) {fprintf(flog,"%s: %u\n", s.c_str(), x);fflush(flog);}
  string tsc4(){QueryPerformanceCounter(&ct);char bb[16];sprintf(bb,"%5.3f",((ct.QuadPart*1000/fq.QuadPart)%10000)/1000.0);return bb;};
  int readmsec(){QueryPerformanceCounter(&ct);return (int)(ct.QuadPart*1000/fq.QuadPart);};
  ~CMyDebug(){fclose(flog);};
};


// === TheTime ===

class TheTime
{
protected:
#ifdef _WIN32_64_
  FILETIME data;
#else
  time_t data;
#endif
//  bool UpToDate;
public:
  TheTime();
#ifdef _WIN32_64_
  void Set(const FILETIME adata) {data = adata;};
#else
#endif
  void Update(); // store current datatime into the data field
  int Difference(const TheTime& t0); // returns difference in seconds
  void SetToPast(const int sec); // set the time to sec seconds to the past 
  bool DateChanged(const TheTime& t0); // returns true if the date differs from that in t0
  int GetDaySec(unsigned int& seconds); // returns the Day of month, 1..31 and store seconds from midnight into seconds
  bool LoadFromString(string str); // Only YYYYMMDDxHHMMSS format supported
  string SaveToString(); // YYYYMMDDxHHMMSS format used
  bool SetSystemTime(); // yes! (for windows, mac needs root permissions)
  string GetStr(bool format=true);
  string GetDateStr();
  string GetTimeStr(string sep="");
};
struct COnceItem
{
  TheTime Last;
  string Name;
};
class COnlyOnce
{
private:
  CRITICAL_SECTION cs;
  map<string, COnceItem> M;
public:
  COnlyOnce();
  bool Start(const string name, const int timeout);
  void Update(const string name);
  ~COnlyOnce();
};


// == TheTimer ==
class TheTimer
{
private:
  volatile bool Enabled, Running; 
  int Interval, rc;
  MMRESULT tid;
  HANDLE hTick, hThread;
  static DWORD WINAPI ThreadFunc(void* param);
  unsigned int Proc();
public:
  TheTimer(int aInterval);
  virtual ~TheTimer();
  virtual void Call();
  void Start();
  void Stop();
  void ComeOn();
};

class CCallBack
{
public:
  virtual int Execute(void* param)=0;
};


// 2. FEC

enum CReadyState {rsEmpty=0, rsFull=1, rsUsed=2};

class CPacket
{
public:
  int Length;
  unsigned char* pData;
//  bool Ready;
  CReadyState Ready;
  CPacket(int aLength);
  ~CPacket();
  void Load(unsigned char* pFrom);
  void JustSave(unsigned char* pTo);
  void Save(unsigned char* pTo);
  void DontNeedIt() {Ready=rsEmpty;};
  void XorLoad(CPacket* pPacket1, CPacket* pPacket2);
};

class CSimpleFEC
{
// Normal Packet: <TAG(4)> <SEQ(1)> <LEN(4)> <DATA(LEN)>
// FEC Packet: <TAG(4)> <SEQ1(1)><SEQ2(1)> <LEN(2)> <XDATA(LEN)>
public:
// Commons
  vector<CPacket*> pPackets; int nPackets, iPacket;
  vector<CPacket*> pParityPackets; int nParityPackets;
  
  int RunLength, iRunLength;
  int SamplesPerPacket, ShuffleNo, BytesPerSample;

  unsigned int
    Tag, // normal tag
    Tag2; // additinal fec tag
  string ErrorText;
//  int TimeLag;
  float PacketRate;
  bool Fec2x;

  CSimpleFEC(int aSamplesPerPacket, int aShuffleNo, int aBytesPerSample, int aRunLength, unsigned int aTag, unsigned int aTag2, bool aFec2x = false);
  CSimpleFEC(int aMaxBytes, int aShuffleNo, unsigned int aTag, unsigned int aTag2, bool aFec2x = false);
  void Create(int aSamplesPerPacket, int aShuffleNo, int aBytesPerSample, int aRunLength, unsigned int aTag, unsigned int aTag2, bool aFec2x = false);
  ~CSimpleFEC();
  static unsigned int SwapOrder4(unsigned int x); // 4-byte
  static unsigned short SwapOrder2(unsigned short x); // 2-byte

// Encoding
  unsigned char* pTmp; int iTmp;
  vector<int> Index;
  int iNext;
  queue<CPacket*> MoreSamples;

  void AddSample(unsigned char* inData);
  void AddSamples(unsigned char* inData, int Length);
  void EatSample();
  void EncProcessPacket();
  void EncProcessParity();
  bool GetPacket(unsigned char* outData, int* outSize); // must be called everytime after AddSample() call

  int GetInterval(); // suggests time interval between sendto calls, msec
  int GetMaxQueue(); // suggests how many messages could be hold in a sendto queue

// Decoding
  vector<CPacket*> pRPackets; CPacket* pSavior;
  void AddNormalPacket(unsigned char* inData, int Length);
  void AddParityPacket(unsigned char* inData, int Length);
  void AddOtherPacket(unsigned char* inData, int Length);
//  queue<int> NewOnes;
  vector<int> NewOnes;
  bool GetSample(unsigned char* outData, unsigned char* outNo);
  bool GetSampleDelayed(unsigned char* outData, unsigned char* outNo, int delay);
  bool IsRecent(int ID);
  int iWait, MaxWait, LastRL, Lastn, LastExport, DetectedDelta;
// stats
  int
    nReceived,
    nReceivedP,
    nReceivedU,
    nRepaired,
    nNotRepaired,
    nTooLate,
    nLost,
    nSame;
  void ClearStats(){nReceived=nReceivedP=nReceivedU=nRepaired=nNotRepaired=nLost=nTooLate=0;};
};

#endif